#include <stdio.h>
#include <stdlib.h>

#include <GL/gl.h>     // The GL Header File
#include <GL/glut.h>   // The GL Utility Toolkit (Glut) Header

#include <time.h>

#include "ALGameLoop.h"
#include "ALGameLoop_private.h"
#include "../common/GameEntity.h"

#include "../common/InputManager.h"

#include "../common/GameLoopConstants.h"

ALGameLoopState alGameLoopState;

void ALGameLoop_start(GameEntity** gameEntitiesArr, int gameEntitiesArrLength) {
	ALGameLoop_initGameLoopState(gameEntitiesArr, gameEntitiesArrLength);
	InputManager_init();
	ALGameLoop_initOpenGL();
}

/*
 * One input event (at most) is processed during an update state.
 * This is not ideal, as, if many input events are generated during a short period of time,
 * the game will give the impression of lagging behind the user's input	AND/OR ignore some input events (such as holding two keys at the same time)
 */
void ALGameLoop_updateState() {
	ArrowKeyPressedEvent* inputEvent = InputManager_popEvent();
	int i;
	for(i=0; i < alGameLoopState.gameEntitiesLength; i++) {
		GameEntity_updateState(alGameLoopState.gameEntities[i], inputEvent);
	}
	alGameLoopState.upsCount++;
}

void ALGameLoop_updateGraphics() {
	/*
	 * Crude way to simulate delays in graphics updates.
	 * This will test weather the game loop handles correctly the fact of skipping frames when graphics drawing takes too much time
	 * in order to have an average constant frequency of calls to updateState
	 */
	if(alGameLoopState.simulatedDelayDurationMs > 0) {
		double now = glutGet(GLUT_ELAPSED_TIME);
		while(((glutGet(GLUT_ELAPSED_TIME)-now)*1000)/(CLOCKS_PER_SEC) < alGameLoopState.simulatedDelayDurationMs) {

		}
	}


	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen
	int i;
	for(i=0; i < alGameLoopState.gameEntitiesLength; i++) {
		GameEntity_updateGraphics(alGameLoopState.gameEntities[i]);
	}
	alGameLoopState.fpsCount++;
	glutSwapBuffers();
}

void ALGameLoop_updateMeasurements() {
	double now = glutGet(GLUT_ELAPSED_TIME);
	double timeElapsedMs = ((now-alGameLoopState.lastMeasurementTime)*1000)/(CLOCKS_PER_SEC);
	if(timeElapsedMs>=500) {
		double totalPrecision = 0;
		int i;
		for(i=0; i < alGameLoopState.gameEntitiesLength; i++) {
			totalPrecision += alGameLoopState.gameEntities[i]->precision;
		}
		double avgPrecision = totalPrecision/((double)alGameLoopState.gameEntitiesLength);

		double ups = (alGameLoopState.upsCount*1000)/timeElapsedMs;
		double fps = (alGameLoopState.fpsCount*1000)/timeElapsedMs;
		char title[100];
		sprintf(title, "C Game Loop Study - Auto Game Loop. FPS:%d UPS:%d SimDelay:%d Precision:%.2f", (int)fps, (int)ups, (int)alGameLoopState.simulatedDelayDurationMs, avgPrecision);
		glutSetWindowTitle(title);
		alGameLoopState.upsCount = 0;
		alGameLoopState.fpsCount = 0;
		alGameLoopState.lastMeasurementTime = now;
	}
}

void ALGameLoop_onLoop() {
	double now = glutGet(GLUT_ELAPSED_TIME);
	double timeElapsedMs = ((now-alGameLoopState.lastLoopTime)*1000)/(CLOCKS_PER_SEC);
	alGameLoopState.timeAccumulatedMs += timeElapsedMs;
	ALGameLoop_updateGraphics();

	while(alGameLoopState.timeAccumulatedMs >= DESIRED_STATE_UPDATE_DURATION_MS) {
		ALGameLoop_updateState();
		alGameLoopState.timeAccumulatedMs -= DESIRED_STATE_UPDATE_DURATION_MS;
	}

	alGameLoopState.lastLoopTime = now;
	ALGameLoop_updateMeasurements();
}

void ALGameLoop_initGameLoopState(GameEntity** gameEntities, int gameEntitiesLength) {
	alGameLoopState.gameEntities = gameEntities;
	alGameLoopState.gameEntitiesLength = gameEntitiesLength;

	alGameLoopState.lastLoopTime = glutGet(GLUT_ELAPSED_TIME);
	alGameLoopState.lastMeasurementTime = glutGet(GLUT_ELAPSED_TIME);

	alGameLoopState.upsCount = 0;
	alGameLoopState.fpsCount = 0;
}

void ALGameLoop_initOpenGL() {
	char *my_argv[] = { "dummyArgs", NULL };
	int   my_argc = 1;
	glutInit(&my_argc, my_argv);

	glShadeModel(GL_SMOOTH);							// Enable Smooth Shading
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);				// Black Background
	glClearDepth(1.0f);									// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);							// Enables Depth Testing
	glDepthFunc(GL_LEQUAL);								// The Type Of Depth Testing To Do
	glEnable(GL_COLOR_MATERIAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(VIEW_WIDTH, VIEW_HEIGHT);
	glutInitWindowPosition(WINDOW_POS_X, WINDOW_POS_Y);
	glutCreateWindow("C Game Loop Study - Auto Looping Game Loop");

	glutDisplayFunc(ALGameLoop_onLoop);
	glutIdleFunc(ALGameLoop_onLoop);
	glutReshapeFunc(ALGameLoop_onWindowReshape);
	glutKeyboardFunc(ALGameLoop_onKeyboard);
	glutMainLoop();
}

void ALGameLoop_onWindowReshape() {
	glMatrixMode(GL_PROJECTION);
	glOrtho(0, VIEW_WIDTH, -VIEW_HEIGHT, 0, 1, 100);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	if (glGetError() != GL_NO_ERROR) {
		printf("Error init-ing OpenGL. \n");
	}
}

void ALGameLoop_onKeyboard(unsigned char key, int x, int y) {
	switch (key) {
	case 27: {            // ESCAPE key
		ALGameLoop_free();
		exit(0);
		break;
	}
	case 'a': {
		InputManager_pushEvent('L');
		break;
	}
	case 'd': {
		InputManager_pushEvent('R');
		break;
	}
	case 'w': {
		InputManager_pushEvent('U');
		break;
	}
	case 's': {
		InputManager_pushEvent('D');
		break;
	}
	case 'o': {
		alGameLoopState.simulatedDelayDurationMs -= 50;
		if(alGameLoopState.simulatedDelayDurationMs < 0) {
			alGameLoopState.simulatedDelayDurationMs = 0;
		}
		break;
	}
	case 'p': {
		alGameLoopState.simulatedDelayDurationMs += 50;
		break;
	}
	}
}

void ALGameLoop_free() {
	int i;
	for(i=0; i < alGameLoopState.gameEntitiesLength; i++) {
		GameEntity_free(alGameLoopState.gameEntities[i]);
	}
	free(alGameLoopState.gameEntities);
	InputManager_free();
	printf("Freed resources\n");
}
